package com.yun.util.listen;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import org.springframework.context.ApplicationContext;

import javax.servlet.ServletOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;


public class ExportProcessor {

    //导出最大线程数...最大页数
    private final static int threadNum_Max = 10;

    //导出查询一次1000
    private final static int page_size = 1000;



    /**
     * 导入
     * @param outputStream  输出流
     * @param className     导出类的名字
     * @param map           导出选择的参数
     */
    public void export(ServletOutputStream outputStream, String className, Map<String, Object> map) throws Exception {
        //类
        Class<?> key = null;
        //实体对象注解值
        Map<Class<?>, List<ExcelProperty>> listMap = ImportReportCenter.getEntity(className);
        for (Map.Entry<Class<?>, List<ExcelProperty>> entry : listMap.entrySet()) {
            if (entry.getValue() != null) {
                key = entry.getKey();
            }
        }
        if (key == null){
            throw new Exception("导出失败，请确定类名是否正确");
        }

        //获取导出对象类注解
        ExcelInit annotation = key.getAnnotation(ExcelInit.class);

        //封装业务方法数据格式
        Method[] methods = arrayMethod(key,annotation);

        //导出文件名
        WriteSheet writeSheet = EasyExcel.writerSheet(annotation.name()).build();

        //封装
        ExcelWriterBuilder writerBuilder = EasyExcel
                .write(outputStream, key)
                .includeColumnFiledNames(getColumn(key));

        //参数转化
        Object obj = getParam(map, key);

        //执行
        execute(obj,annotation.implType(),methods,writeSheet,writerBuilder.build());
    }


    /**
     * 执行导出
     * @param paramObj    参数对象
     * @param clazz       业务类
     * @param methods     方法
     * @param writeSheet  excel组件
     * @param excelWriter excel组件
     * @throws Exception  错误
     */
    private void execute(Object paramObj, Class<?> clazz, Method[] methods, WriteSheet writeSheet, ExcelWriter excelWriter) throws Exception{
        //查询结果集合
        List<Object> result;

        //业务类实例
        Object instance = clazz.newInstance();

        //执行查询方法
        Method method = methods[0];
        Object o = method.invoke(instance,paramObj,ImportReportCenter.getContext());

        //查询次数
        int threadNum = Math.toIntExact(((Long) o / page_size) + 1);

        //限制数据导出最大次数
        if (threadNum>10){
            threadNum = threadNum_Max;
        }

        //异步执行队列
        ArrayBlockingQueue<List<Object>> queue = new ArrayBlockingQueue<>(threadNum, true);

        //异步导出执行次数监控
        AtomicInteger atomicInteger=new AtomicInteger(threadNum);

        //多线程查询
        executeTask(queue,threadNum,instance,methods[1],paramObj);

        //异步写入excel
        try {
            while ((result = queue.poll(60, TimeUnit.SECONDS)) != null) {
                excelWriter.write(result,writeSheet);
                if(0==atomicInteger.decrementAndGet()){
                    break;
                }
            }
        }catch (InterruptedException e){
            e.getMessage();
        }finally {
            if(excelWriter != null ){
                excelWriter.finish();//关闭
            }
        }
    }

    private void executeTask(ArrayBlockingQueue<List<Object>> queue, int loopNum, Object instance, Method method, Object paramObj) {
        ExecutorService executor = Executors.newFixedThreadPool(loopNum);
        for (int i = 0; i < loopNum; i++) {
            int finalI = i;
            executor.execute(() -> {
                try {
                    Object invoke = method.invoke(instance, finalI,page_size,paramObj,ImportReportCenter.getContext());
                    queue.add((List<Object>) invoke);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    e.printStackTrace();
                }
            });
        }
    }


    /**
     * 获取对象导出列
     * @param clazz  类对象
     * @return  列集合
     */
    private List<String> getColumn(Class<?> clazz) {
        List<String> list = new ArrayList<>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            if (annotation!=null){
                list.add(field.getName());
            }
        }
        return list;
    }


    /**
     * 业务类执行方法
     * @param annotation   自定义导出注解类的实例
     * @param key          实体类
     * @return  方法数组
     */
    private Method[] arrayMethod(Class<?> key, ExcelInit annotation) throws NoSuchMethodException {
        Class<?> type = annotation.implType();
        Method countMethod = type.getMethod(annotation.countMethodName(), key, ApplicationContext.class);
        Method pageMethod = type.getMethod(annotation.pageMethodName(), int.class,int.class,key, ApplicationContext.class);
        Method[] methods = new Method[2];
        methods[0] = countMethod;
        methods[1] = pageMethod;
        return methods;
    }


    /**
     * 参数转化
     * @param map      前端参数
     * @param clazz    转化对象
     * @return  对象实例
     */
    private Object getParam(Map<String, Object> map, Class<?> clazz) {
        Map<String, Object> paramMap = new HashMap<>();
        Collection<String> set = map.keySet();
        for (String s : set) {
            Object o = map.get(s);
            if (o instanceof Integer){
                paramMap.put(s,o);
                continue;
            }
            if (o!=null){
                paramMap.put(s,o.toString());
            }
        }
        return MapUtil.mapToObject(paramMap, clazz);
    }
}
